#define BUFFER_OFFSET(bytes) ((GLubyte*) NULL + (bytes))

#include "Model.h"

#include "GraphicsLayer.h"

using namespace std;

cModel::cModel(
    std::vector<GLuint> CordIdList,
    std::vector<GLuint> TextCoordsIdList,
    std::vector<GLuint> ColorsIdList,
    std::vector<GLuint> MatIdsList,
    std::vector<GLfloat> Vertices,
    std::vector<GLfloat> Normals,
    std::vector<GLfloat> Colors,
    std::vector<GLfloat> TexCoords,
    std::string name,
    int matID
)
{
    // *************** CordIdList ***************
    this->m_pCordIdList.reserve(CordIdList.size());
    this->m_pCordIdList.swap(CordIdList);
    CordIdList.clear();
    
    // *************** TextCoordsIdList ***************
    this->m_pTextCoordsIdList.reserve(TextCoordsIdList.size());
    this->m_pTextCoordsIdList.swap(TextCoordsIdList);
    TextCoordsIdList.clear();
    
    // *************** ColorsIdList ***************
    this->m_pColorsIdList.reserve(ColorsIdList.size());
    this->m_pColorsIdList.swap(ColorsIdList);
    ColorsIdList.clear();
    
    // *************** MatIdsList ***************
    this->m_pMatIdsList.reserve(Vertices.size());
    this->m_pMatIdsList.swap(MatIdsList);
    MatIdsList.clear();
    
    // *************** Vertices ***************
    this->m_pVertices.reserve(Vertices.size());
    this->m_pVertices.swap(Vertices);
    Vertices.clear();
    
    // *************** Normals ***************
    this->m_pNormals.reserve(Normals.size());
    this->m_pNormals.swap(Normals);
    Normals.clear();
    
    // *************** Colors ***************
    this->m_pColors.reserve(Colors.size());
    this->m_pColors.swap(Colors);
    Colors.clear();
    
    // *************** TexCoords ***************
    this->m_pTexCoords.reserve(TexCoords.size());
    this->m_pTexCoords.swap(TexCoords);
    TexCoords.clear();
    
    // *************************************************

    this->name = name;
    this->matID = matID;
            
    this->CreateBufferFromXML();

    this->m_pTexture = Graphics()->LoadTextureFromGraphics(this->matID);
    int shader = Graphics()->getProgramFromDefaultShader();
}

void cModel::CreateBuffer() {
    int elementPerVertex = 11;
    
    // Lo que pesa cada vértice: 3 float's para coordenadas, 3 float's para color, 3 float's para normales, 2 floats's para textura
    int sizeOfVertice = (elementPerVertex * sizeof (float));
    
    // El tamaño del buffer es la cantidad de vértices por el tamaño de un vértice
    int sizeOfBuffer = (this->vertexCount  * sizeOfVertice);
    
    // Crear el buffer de datos
    glGenBuffers(1, &this->m_pTriangleVBO);
    // Enlazar el buffer al objeto que creamos para el mismo
    glBindBuffer(GL_ARRAY_BUFFER, this->m_pTriangleVBO);
    // Asignar memoria en la placa de video para nuestro buffer
    glBufferData(GL_ARRAY_BUFFER, sizeOfBuffer, &this->listXmlVertexBuffer[0], GL_STATIC_DRAW);
    
    // Crear el buffer de índices
    glGenBuffers(1, &this->m_pTriangleIBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_pTriangleIBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof (ushort) * this->indexCount, &this->listXmlIndexs[0], GL_STATIC_DRAW);
}

cModel::~cModel() {
    glDeleteBuffers(1, &this->m_pTriangleIBO);
    glDeleteBuffers(1, &this->m_pTriangleVBO);
}

void cModel::Scale(float amt) {
}

void cModel::Update(float timeDelta) {
    this->UpdateTransform();

    //update de las variables de Matrices y luces
    Graphics()->UpdateMatrices();
    Graphics()->UpdateLights();
}

void cModel::Draw() {
    
    Graphics()->EnableDefaultShader();

    Graphics()->SendMatrix();
    
    Graphics()->SetTexture(m_pTexture);

    // *************** Se dibuja el triangulo ***************    

    this->EnableClient();
    
    // Comando que dibuja el triángulo
    glDrawElements(GL_TRIANGLES, this->vertexCount, GL_UNSIGNED_SHORT, 0);

    this->DisableClient();

    // *************** Se dibuja el triangulo ***************    

    Graphics()->DisableDefaultShader();
}

void cModel::UpdateTransform() {
    /*glm::mat4 Model = Graphics()->matrixModel;
    Model = glm::rotate(Model, this->RotateModelData.x, glm::vec3(1.0f, 0.0f, 0.0f));
    Model = glm::rotate(Model, this->RotateModelData.y, glm::vec3(0.0f, 1.0f, 0.0f));
    Model = glm::rotate(Model, this->RotateModelData.z, glm::vec3(0.0f, 0.0f, 1.0f));
    
    Graphics()->matrixModel = Model;*/
    Graphics()->RotateModelData.x = RotateModelData.x;
    Graphics()->RotateModelData.y = RotateModelData.y;
    Graphics()->RotateModelData.z = RotateModelData.z;
}

void cModel::RotateModelInX(float angle) {
    this->RotateModelData.x = angle;
}

void cModel::RotateModelInY(float angle) {
    this->RotateModelData.y = angle;
}

void cModel::RotateModelInz(float angle) {
    this->RotateModelData.z = angle;
}

void cModel::ScaleModel(float x, float y, float z) {
    Graphics()->ScaleModelData[0] = x; // x
    Graphics()->ScaleModelData[1] = y; // y
    Graphics()->ScaleModelData[2] = z; // z
}

void cModel::TranslateModel(float x, float y, float z) {
    Graphics()->TranslateModelData[0] = x; // x
    Graphics()->TranslateModelData[1] = y; // y
    Graphics()->TranslateModelData[2] = z; // z
}

float cModel::GenRadius() {
}

void cModel::EnableClient() {
    // Enlazar el buffer al objeto que creamos para el mismo
    glBindBuffer(GL_ARRAY_BUFFER, this->m_pTriangleVBO);
    // Lo que pesa cada vértice: 3 float's para coordenadas, 3 float's para color, 3 float's para normales, 2 floats's para textura
    int sizeOfVertice = (11 * sizeof (float));
    
    // Habilitar y especificar los punteros a los arrays
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // Indicar la forma en que está ordenado nuestro buffer
    // BUFFER_OFFSET(0) indica que los vertices empiezan en el principio del array
    glVertexPointer(3, GL_FLOAT, sizeOfVertice, BUFFER_OFFSET(0));
    // BUFFER_OFFSET(3 * sizeof(float)) indica que los colores empiezan luego de los vertices
    glColorPointer(3, GL_FLOAT, sizeOfVertice, BUFFER_OFFSET(3 * sizeof (float)));
    // No tiene (size) ya que siempre son 3.
    // BUFFER_OFFSET(6 * sizeof(float)) indica que las normales empiezan luego de los colores
    glNormalPointer(GL_FLOAT, sizeOfVertice, BUFFER_OFFSET(6 * sizeof (float)));
    // BUFFER_OFFSET(9 * sizeof(float)) indica que las texturas empiezan luego de las normales
    glTexCoordPointer(2, GL_FLOAT, sizeOfVertice, BUFFER_OFFSET(9 * sizeof (float)));
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_pTriangleIBO);    
    
}

void cModel::DisableClient() {
    // Deshabilitar y especificar los punteros a los arrays
    glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // Desenlazar el buffer al objeto que creamos para el mismo
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void cModel::CreateBufferFromXML()
{    
    int m_pCordIDListSize = this->m_pCordIdList.size();
    this->vertexCount = m_pCordIDListSize;
    
    // Obtener el largo del array a enviar al buffer
    int m_pCordIdListSize = (m_pCordIDListSize * 3);
    //                          Vertices                 texturas                        normales                 colores
    int sizeVertexArray = (m_pCordIdListSize) + (this->m_pTextCoordsIdList.size() * 2) + (m_pCordIdListSize) + (this->m_pColorsIdList.size() * 3);
    
    // Se completa el array de vertices
    int i = 0;
    int actualIndex = 0;
    
    // Se completa el array listXmlVertexBuffer
    //vector<GLfloat> listXmlVertexBuffer;
    this->listXmlVertexBuffer.reserve(sizeVertexArray);
    
    for(i=0; i<m_pCordIDListSize; i++) {
        // ********** Completar las coordenadas **********
        actualIndex = this->m_pCordIdList[i] * 3;
        this->listXmlVertexBuffer.push_back(this->m_pVertices[actualIndex]);
        this->listXmlVertexBuffer.push_back(this->m_pVertices[actualIndex + 1]);
        this->listXmlVertexBuffer.push_back(this->m_pVertices[actualIndex + 2]);
        
        // ********** Completar los colores **********
        actualIndex = this->m_pColorsIdList[i] * 3;
        this->listXmlVertexBuffer.push_back(this->m_pColors[actualIndex]);
        this->listXmlVertexBuffer.push_back(this->m_pColors[actualIndex + 1]);
        this->listXmlVertexBuffer.push_back(this->m_pColors[actualIndex + 2]);
        
        // ********** Completar las normales **********
        actualIndex = this->m_pCordIdList[i] * 3;
        this->listXmlVertexBuffer.push_back(this->m_pNormals[actualIndex]);
        this->listXmlVertexBuffer.push_back(this->m_pNormals[actualIndex + 1]);
        this->listXmlVertexBuffer.push_back(this->m_pNormals[actualIndex + 2]);
        
        // ********** Completar las texturas **********
        actualIndex = this->m_pTextCoordsIdList[i] * 2;
        this->listXmlVertexBuffer.push_back(this->m_pTexCoords[actualIndex]);
        this->listXmlVertexBuffer.push_back(this->m_pTexCoords[actualIndex + 1]);
    }
    
    i = 0;
    
    ushort valorActual = 0;
    
    // Se completa el array listXmlIndexs
    //vector<ushort> listXmlIndexs;
    this->listXmlIndexs.reserve(this->vertexCount);
    this->indexCount = this->vertexCount;
    for(i = 0; i < this->vertexCount; i++) {
        this->listXmlIndexs.push_back(valorActual);
        valorActual ++;
    }
    
    // Limpiar los arrays de indices de vectores
    this->m_pCordIdList.clear();
    this->m_pColorsIdList.clear();
    this->m_pTextCoordsIdList.clear();
    
    // Limpiar los vectores propiamente dichos
    this->m_pVertices.clear();
    this->m_pColors.clear();
    this->m_pNormals.clear();
    this->m_pTexCoords.clear();
    
    this->CreateBuffer();
    
    // Limpiar los vectores de buffer
    this->listXmlVertexBuffer.clear();
    this->listXmlIndexs.clear();
}

void cModel::LogCreateBuffer(int sizeOfVertice, int elementPerVertex, int sizeOfBuffer){
    printf("*********** inicio Datos CreateBuffer ***********\n");
    printf("this->m_pTriangleIBO: %i \n", this->m_pTriangleIBO);
    printf("this->m_pTriangleVBO: %i \n", this->m_pTriangleVBO);
    printf("Tamanio del vertice calculado (sizeOfVertice): %i bytes\n", sizeOfVertice);
    printf("Cantidad de floats por vertice (elementPerVertex): %i\n\n", elementPerVertex);
    printf("Cantidad de vertices previamente contados (vertexCount): %i\n", vertexCount);
    printf("Tamanio del buffer calculado (sizeOfBuffer): %i bytes\n", sizeOfBuffer);
    
    int total = this->listXmlVertexBuffer.size();
    printf("Cantidad de vertices en XmlVertexBuffer (total bytes / sizeOfVertice): %i\n", total / elementPerVertex);
    printf("Tamanio real de listXmlVertexBuffer: %i bytes \n", total * sizeof (float));
    printf("Cantidad de elemtos en XmlVertexBuffer: %i\n", total);
    
    int i=0;
    for(;i<total;i++){
        
        if(i==total - 11){
            printf("\nUltimo vertice:\n");
            printf("*****************************************************************\n");
            printf("pos:\n");
            printf("x: %f \n", this->listXmlVertexBuffer[i]);
        }
        if(i==total - 10){
            printf("y: %f \n", this->listXmlVertexBuffer[i]);
        }
        if(i==total - 9){
            printf("z: %f \n", this->listXmlVertexBuffer[i]);
        }

        if(i==total - 8){
            printf("colors:\n");
            printf("0: %f \n", this->listXmlVertexBuffer[i]);
        }
        if(i==total - 7){
            printf("1: %f \n", this->listXmlVertexBuffer[i]);
        }
        if(i==total - 6){
            printf("2: %f \n", this->listXmlVertexBuffer[i]);
        }
        
        if(i==total - 5){
            printf("normals:\n");
            printf("x: %f \n", this->listXmlVertexBuffer[i]);
        }
        if(i==total - 4){
            printf("y: %f \n", this->listXmlVertexBuffer[i]);
        }
        if(i==total - 3){
            printf("z: %f \n", this->listXmlVertexBuffer[i]);
        }

        if(i==total - 2){
            printf("texture:\n");
            printf("x: %f \n", this->listXmlVertexBuffer[i]);
        }
        if(i==total - 1){
           printf("y: %f \n", this->listXmlVertexBuffer[i]);
        }
        
        if(i == total - 1){
            printf("\nTermino en el %i \n", total);
        }
    }
    
    printf("\n");
            
    total = this->listXmlIndexs.size();
    printf("Tamanio del buffer de indices calculado: %i bytes\n", sizeof (ushort) * this->indexCount);
    printf("Cantidad de elementos en listXmlIndexs: %i\n", total);
    printf("Tamanio real de listXmlIndexs: %i bytes \n", total * sizeof (ushort));
    i=0;
    for(;i<total;i++){
        if(i ==0){
            printf("Primer ushort en listXmlIndexs (%i): %i \n", i + 1 , this->listXmlIndexs[i]);
        }
        if(i == total -1){
            printf("Ultimo ushort en listXmlIndexs (%i): %i \n", i + 1 , this->listXmlIndexs[i]);
        }
    }
    printf("*********** fin Datos CreateBuffer ***********\n");
}